package org.fatewa.engine.genius.base;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import core.MultipartData;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.logging.log4j.util.Strings;
import org.fatewa.engine.advice.exception.RayquazaException;
import org.fatewa.engine.design.base.PrimaryKeyProvider;
import org.fatewa.engine.design.genius.GenericHooks;
import org.fatewa.engine.genius.annotations.Relation;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import weapon.KeyWeapons;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.fatewa.engine.genius.utils.GenericUtils.*;

/**
 * 通用的关联关系接口生成通用控制器，
 * 整合了 {@link GenericHooks} 使得关联表的 hook 拦截成为可能，通用接口可以实现更多机制
 * 在后续计划中也会继续整合下面的功能
 * {@link org.fatewa.engine.experimental.poi.WorkBookIntermediary}
 * {@link org.fatewa.engine.genius.annotations.Public}
 * {@link org.fatewa.engine.design.genius.GenericProjector}
 * 在路径中增加了 [TARGET] 表达式
 * 在实际映射中，
 * 该表达式会被转换为对应的实体名称 {@link TE}
 * 具体实现可参考 {@link org.fatewa.engine.endpoint.RayquazaEndpoint} getTargetPath 函数
 *
 * @param <E>  当前实体
 * @param <RE> 关联连接器
 * @param <TE> 目标实体
 * @param <P>  主键类型
 * @author fatewa
 */
@RestController
@Data
@Accessors(fluent = true, chain = true)
@Slf4j
public class GenericRelationController<E, RE, TE, P extends Serializable> {
    /**
     * 基准的实体类
     */
    private Class<TE> targetEntityClass;
    /**
     * 目标类
     */
    private Class<RE> relationEntityClass;

    /**
     * 基准实体类的关联类控制器
     */
    private IService<TE> targetService;

    /**
     * 根节点实体
     */
    private IService<E> entityService;

    /**
     * 根节点实体类型
     */
    private Class<E> entityClass;

    /**
     * 基准实体类的关联类的 service
     */
    private IService<RE> relationService;

    /**
     * 主键提供器
     */
    private PrimaryKeyProvider<P> provider;

    /**
     * Spring 上下文
     */
    private ApplicationContext context;

    /**
     * 全部 hooks
     * 系统内的全部 hook
     */
    private Map<Class<?>, Collection<GenericHooks<?, ?>>> hooks;

    private Relation relations;

    /**
     * 获取当前实体的 key
     *
     * @return 前实体的 key
     */
    private String getEntityKey() {
        String key = relations.connector().key();
        if (Strings.isNotEmpty(key)) {
            return key;
        }
        return this.getKey(entityClass);
    }

    /**
     * 获取关联实体的 key
     *
     * @return 关联实体的 key
     */
    private String getRelationKey() {
        String key = relations.connector().targetKey();
        if (Strings.isNotEmpty(key)) {
            return key;
        }
        return this.getKey(targetEntityClass);
    }

    /**
     * 获取字段名称
     *
     * @param entityClass 实体类型
     * @return 名称
     */
    private String getKey(Class<?> entityClass) {
        TableName tableName = entityClass.getDeclaredAnnotation(TableName.class);
        if (null != tableName) {
            return String.format("%s_%s", tableName.value(), provider.key());
        }
        return String.format("%s_%s", entityClass.getSimpleName().toLowerCase(), provider.key());
    }

    @GetMapping("/{domainId}/[TARGET]")
    public IPage<TE> query(@PathVariable P domainId, HttpServletRequest request) {

        P primaryKey = primaryKey(provider, domainId);

        // 从当前实体拉出的关联关系集合
        Page<RE> page = relationService
                .query()
                .eq(getEntityKey(), primaryKey)
                .page(new Page<>());

        List<RE> records = page.getRecords();

        if (records.size() == 0) {
            throw new RayquazaException(HttpStatus.NOT_FOUND, "No data :(");
        }

        List<P> primary = records.stream().map(it -> {
            MultipartData multipartData = new MultipartData();
            multipartData.parties(it);
            return multipartData.getPart(getRelationKey(), provider.type(), true);
        }).collect(Collectors.toList());

        List<TE> list = targetService.query()
                .in(provider.key(), primary)
                .list();

        IPage<TE> result = new Page<>();
        BeanUtil.copyProperties(page, result);
        result.setRecords(list);

        return result;
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键
     * @return 根据主键查询到的单个实体
     */
    @GetMapping("/{domainId}/[TARGET]/{id}")
    public TE single(@PathVariable P id, @PathVariable P domainId) {
        this.checkRelation(domainId, id);

        TE instance = targetService
                .query()
                .eq(provider.key(), primaryKey(provider, id))
                .one();

        // spring boot 3.x 返回 null 时不会触发 advice，故需要自行处理一下
        if (null == instance) {
            throw new RayquazaException(HttpStatus.NOT_FOUND, "找不到对应数据");
        }

        return instance;
    }

    /**
     * 校验关联关系是否存在
     *
     * @param domainId 主领域主键
     * @param id       关联实体主键
     */
    private void checkRelation(P domainId, P id) {


        try {
            RE relation = relationService
                    .query()
                    .eq(getEntityKey(), primaryKey(provider, domainId))
                    .eq(getRelationKey(), primaryKey(provider, id))
                    .one();

            if (null == relation) {
                throw new RayquazaException(HttpStatus.NOT_FOUND,
                        String.format("%s:%s has no relation with %s:%s",
                                entityClass.getSimpleName().toLowerCase(),
                                domainId,
                                targetEntityClass.getSimpleName().toLowerCase(), id));
            }

        } catch (TooManyResultsException tooManyResultsException) {
            throw new RayquazaException(HttpStatus.CONFLICT, "your request is not 1 to n model, not supported");
        }

    }

    /**
     * 解除关联关系，但不删除对应实体
     *
     * @param id 主键
     */
    @DeleteMapping("/{domainId}/[TARGET]/{id}")
    public void delete(@PathVariable P id, @PathVariable P domainId, HttpServletRequest request) {
        // 校验关联关系
        this.checkRelation(domainId, id);

        QueryWrapper<RE> wrapper = Wrappers.query();

        wrapper.eq(getEntityKey(), primaryKey(provider, domainId))
                .eq(getRelationKey(), primaryKey(provider, id));

        try {

            // 查找出关联表
            RE rel = relationService.query()
                    .eq(getEntityKey(), primaryKey(provider, domainId))
                    .eq(getRelationKey(), primaryKey(provider, id))
                    .one();

            // 取出当前关联实体的 hook
            Collection<GenericHooks<?, ?>> genericHooks = hooks.get(relationEntityClass);

            Function<GenericHooks<RE, P>, GenericHooks<RE, P>> beforeDelete = relationBeforeDeleteHook(request, rel);
            runRelationEntityHook(genericHooks, beforeDelete);

            // 整体包裹在事务内
            transaction((manager, status) -> {
                // 移除关联关系
                boolean result = relationService.remove(wrapper);
                Function<GenericHooks<RE, P>, GenericHooks<RE, P>> deleted = relationDeletedHook(rel, result);
                runRelationEntityHook(genericHooks, deleted);
            }, context);

        } catch (TooManyResultsException tooManyResultsException) {
            throw new RayquazaException(HttpStatus.CONFLICT, "your request is not 1 to n model, not supported");
        }
    }

    /**
     * 对目标实体进行增量/补丁更新
     */
    @PatchMapping("/{domainId}/[TARGET]")
    public TE update(@RequestBody TE target, @PathVariable P domainId, HttpServletRequest request) {
        // 获取主键
        P primary = getPrimaryKey(provider, target);

        TE entity = newEntity(targetEntityClass, target);

        // 校验关联关系
        checkRelation(domainId, primary);

        QueryWrapper<TE> wrapper = Wrappers.query();
        wrapper.eq(provider().key(), primary);

        // 整体包裹在事务内
        transaction((manager, status) -> {
            // 运行更新之前 hook
            runTargetEntityHook(hooks.get(targetEntityClass), this.targetBeforeUpdateHook(entity, request));
            // 执行数据保存
            boolean isSuccess = targetService.update(entity, wrapper);
            // 运行更新之后 hook
            runTargetEntityHook(hooks.get(targetEntityClass), this.targetUpdatedHook(entity, isSuccess));
        }, context);

        return target;
    }

    /**
     * 关联实体删除成功的 hook
     *
     * @param rel    关联实体
     * @param result 删除结果
     * @return hook
     */
    @NotNull
    private Function<GenericHooks<RE, P>, GenericHooks<RE, P>> relationDeletedHook(RE rel, boolean result) {
        return (hook) -> {
            // 开启 hook 并且开启事务
            if (hook.transactional()) {
                transaction((manager, status) -> hook.deleted(rel, result), context);
            } else {
                hook.deleted(rel, result);
            }
            return hook;
        };
    }


    /**
     * 关联实体更新成功的 hook
     *
     * @param target  目标实体
     * @param request 请求
     * @return hook
     */
    @NotNull
    private Function<GenericHooks<TE, P>, GenericHooks<TE, P>> targetBeforeUpdateHook(TE target, HttpServletRequest request) {
        return (hook) -> {
            // 开启 hook 并且开启事务
            if (hook.transactional()) {
                transaction((manager, status) -> hook.beforeUpdate(target, request), context);
            } else {
                hook.beforeUpdate(target, request);
            }
            return hook;
        };
    }

    /**
     * 关联实体更新成功的 hook
     *
     * @param target 目标实体
     * @param result 更新结果
     * @return hook
     */
    @NotNull
    private Function<GenericHooks<TE, P>, GenericHooks<TE, P>> targetUpdatedHook(TE target, boolean result) {
        return (hook) -> {
            // 开启 hook 并且开启事务
            if (hook.transactional()) {
                transaction((manager, status) -> hook.updated(target, result), context);
            } else {
                hook.updated(target, result);
            }
            return hook;
        };
    }

    /**
     * 关联实体删除之前的 hook
     *
     * @param rel     关联实体
     * @param request 请求
     * @return hook
     */
    @NotNull
    private Function<GenericHooks<RE, P>, GenericHooks<RE, P>> relationBeforeDeleteHook(HttpServletRequest request, RE rel) {
        // 删除之前的 hook
        return (hook) -> {
            // 开启 hook 并且开启事务
            if (hook.transactional()) {
                transaction((manager, status) -> hook.beforeDelete(rel, request), context);
            } else {
                hook.beforeDelete(rel, request);
            }
            return hook;
        };
    }


    /**
     * 在主领域下创建关联目标实体
     */
    @PostMapping("/{domainId}/[TARGET]")
    public TE create(@RequestBody TE targetEntity, @PathVariable P domainId, HttpServletRequest request) {

        boolean crossOrigin = !relationEntityClass.equals(targetEntityClass);

        // 处理 Spring 内部的 Map
        TE translated = newEntity(targetEntityClass, targetEntity);

        transaction((manager, status) -> {
            try {
                // 获取目标实体创建之前的 hook runner
                Function<GenericHooks<TE, P>, GenericHooks<TE, P>> beforeCreateTargetEntity = beforeCreateTargetEntityHook(targetEntity, request);

                // 获取目标实体所有的 hook
                Collection<GenericHooks<?, ?>> targetHooks = hooks.get(targetEntityClass);
                runTargetEntityHook(targetHooks, beforeCreateTargetEntity);

                // 同源时，直接绑定至目标实体即可
                if (!crossOrigin) {
                    ReflectUtil.setFieldValue(translated, KeyWeapons.convert(getEntityKey()), domainId);
                }

                // 执行数据保存
                final boolean success = targetService.save(translated);

                // 获取目标实体创建之后的 hook runner
                Function<GenericHooks<TE, P>, GenericHooks<TE, P>> createdTargetEntity = createdTargetEntityHook(targetEntity, success);
                // 运行目标实体的 hook
                runTargetEntityHook(targetHooks, createdTargetEntity);
                // 关联实体与目标实体类型不同源时执行
                if (crossOrigin) {
                    // relation entity
                    RE relationEntity = relationEntityClass.newInstance();
                    // 设置关联
                    ReflectUtil.setFieldValue(relationEntity, KeyWeapons.convert(getEntityKey()), domainId);

                    // 新的主键
                    P primary = getPrimaryKey(provider, targetEntity);
                    ReflectUtil.setFieldValue(relationEntity, KeyWeapons.convert(getRelationKey()), primary);

                    // 获取关联实体创建之前的 hook runner
                    Function<GenericHooks<RE, P>, GenericHooks<RE, P>>
                            beforeCreateRelationEntity = beforeCreateRelationEntityHook(relationEntity, request);

                    // 获取关联实体的所有 hook
                    Collection<GenericHooks<?, ?>> relationHooks = hooks.get(relationEntityClass);

                    // 运行关联实体的 before create hook
                    runRelationEntityHook(relationHooks, beforeCreateRelationEntity);

                    // 绑定关系
                    relationService.save(relationEntity);

                    // 获取关联实体创建之后的 hook runner
                    Function<GenericHooks<RE, P>, GenericHooks<RE, P>>
                            createdRelationEntity = createdRelationEntityHook(relationEntity, success);

                    // 运行关联实体的 created hook
                    runRelationEntityHook(relationHooks, createdRelationEntity);
                }
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }, context);

        return targetEntity;
    }

    /**
     * 关联实体创建成功的 hook
     *
     * @param relationEntity 关联实体
     * @param success        是否创建成功
     * @return hook
     */
    private Function<GenericHooks<RE, P>, GenericHooks<RE, P>> createdRelationEntityHook(RE relationEntity, boolean success) {
        return (hook) -> {
            if (hook.transactional()) {
                transaction((manager, status) -> hook.created(relationEntity, success), context);
            } else {
                hook.created(relationEntity, success);
            }
            return hook;
        };
    }

    /**
     * 关联实体的 hook 创建
     *
     * @param relationEntity 关联实体
     * @param request        请求
     * @return hook
     */
    private Function<GenericHooks<RE, P>, GenericHooks<RE, P>> beforeCreateRelationEntityHook(RE relationEntity, HttpServletRequest request) {
        return (hook) -> {
            if (hook.transactional()) {
                transaction((manager, status) -> hook.beforeCreate(relationEntity, request), context);
            } else {
                hook.beforeCreate(relationEntity, request);
            }
            return hook;
        };
    }

    /**
     * 目标实体创建成功的 hook
     *
     * @param targetEntity 目标实体
     * @param success      是否创建成功
     * @return hook
     */
    @NotNull
    private Function<GenericHooks<TE, P>, GenericHooks<TE, P>> createdTargetEntityHook(TE targetEntity, boolean success) {
        return (hook) -> {
            if (hook.transactional()) {
                transaction((manager, status) -> hook.created(targetEntity, success), context);
            } else {
                hook.created(targetEntity, success);
            }
            return hook;
        };
    }

    /**
     * @param targetEntity 目标实体
     * @param request      请求
     * @return hook
     */
    @NotNull
    private Function<GenericHooks<TE, P>, GenericHooks<TE, P>> beforeCreateTargetEntityHook(TE targetEntity, HttpServletRequest request) {
        return (hook) -> {
            if (hook.transactional()) {
                transaction((manager, status) -> hook.beforeCreate(targetEntity, request), context);
            } else {
                hook.beforeCreate(targetEntity, request);
            }
            return hook;
        };
    }

    /**
     * 运行目标实体的 Hook
     *
     * @param targetHooks hook 集合
     * @param runner      hook 执行函数
     */
    private void runTargetEntityHook(Collection<GenericHooks<?, ?>> targetHooks,
                                     Function<GenericHooks<TE, P>, GenericHooks<TE, P>> runner) {
        for (GenericHooks<?, ?> genericHook : orderedHooks(targetHooks)) {
            //noinspection unchecked
            GenericHooks<TE, P> hook = (GenericHooks<TE, P>) genericHook;
            if (hook.async()) {
                CompletableFuture.runAsync(() -> runner.apply(hook));
            } else {
                runner.apply(hook);
            }
        }
    }

    /**
     * 运行关联实体的 Hook
     *
     * @param targetHooks hook 集合
     * @param runner      hook 执行函数
     */
    private void runRelationEntityHook(Collection<GenericHooks<?, ?>> targetHooks,
                                       Function<GenericHooks<RE, P>, GenericHooks<RE, P>> runner) {
        for (GenericHooks<?, ?> genericHook : orderedHooks(targetHooks)) {
            //noinspection unchecked
            GenericHooks<RE, P> hook = (GenericHooks<RE, P>) genericHook;
            if (hook.async()) {
                CompletableFuture.runAsync(() -> runner.apply(hook));
            } else {
                runner.apply(hook);
            }
        }

    }

}
